package jsr292.java.lang.invoke;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;

import jsr292.weaver.opt.Optimizer;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerVarAllocator;

public final class SwitchPoint {
    public volatile boolean switchz;        // accessed using the method handle below
    final MethodHandle switchTest;
    ArrayList<CallSite> dependencies;       // dependencies can be used only in a synchronized block on switchTest

    private static final MethodHandle STATIC_SWITCH;
    static {
        try {
            Field field = SwitchPoint.class.getField("switchz"); 
            STATIC_SWITCH = MethodHandles.publicLookup().unreflectGetter(field);
        } catch (NoSuchFieldException e) {
            throw (AssertionError)new AssertionError().initCause(e);
        } catch(IllegalAccessException e) {
            throw (AssertionError)new AssertionError().initCause(e);
        } catch (SecurityException e) {
            throw (AssertionError)new AssertionError().initCause(e);
        }
    }

    public SwitchPoint() {
        this.switchz = true;
        this.switchTest = MethodHandles.insertArguments(STATIC_SWITCH, 0, this);  // bind to current switcher
    }
    
    public boolean hasBeenInvalidated() {
        return !switchz;
    }

    public static void invalidateAll(SwitchPoint... switchPoints) {
        HashSet<Class<?>> classToRedefines = new HashSet<Class<?>>();
        for(SwitchPoint switchPoint: switchPoints) {
            synchronized (switchPoint.switchTest) {
                switchPoint.switchz = false;    
                
                ArrayList<CallSite> dependencies = switchPoint.dependencies;
                if (dependencies != null) {
                    for(CallSite callSite: dependencies) {
                        // change counters of each callsite and get caller class
                        BootstrapCallSite.invalidateAndPopulateCallerClassSet(callSite, classToRedefines);
                    }
                    switchPoint.dependencies = null;
                }
            }
        }
        
        // redefine all classes containing callsites
        if (!classToRedefines.isEmpty()) {
            synchronized(CallSite.LOCK) {
                Optimizer.retransform(classToRedefines);
            }
        }
    }

    public MethodHandle guardWithTest(MethodHandle target, MethodHandle fallback) {
        if (switchz == false) {
            return fallback;
        }
        MethodHandle test = MethodHandles.checkGuardWithTest(switchTest, target, fallback);
        return new MHSwitchPointGuard(test, target, fallback);
    }
    
    class MHSwitchPointGuard extends MHGuarder {
        MHSwitchPointGuard(MethodHandle test, MethodHandle target, MethodHandle fallback) {
            super(test, target, fallback);
        }
        
        @Override
        public boolean isWeavable(Class<?> classBeingRedefined) {
            // FIXME, weave() should implement a backoff algorithm
            // we should only test target or fallback but switchz can be changed
            // between the call to isWeavable() and weave() and there is no way for weave to backoff
            return super.isWeavable(classBeingRedefined);
        }
        
        @Override
        public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
            if (switchz == false) {
                fallback.weave(context, allocator);
                return;
            }
            
            // if it's a method call, not an invokedynamic
            CallSite callSite = context.getCallSite();
            if (callSite == null) {
                super.weave(context, allocator);
                return;
            }
            
            // add a dependency between the current switch point and the callsite
            synchronized(switchTest) {
                if (dependencies == null) {
                    dependencies = new ArrayList<CallSite>();
                }
                dependencies.add(callSite);
            }
            target.weave(context, allocator);
        }
    }
}
